home *** CD-ROM | disk | FTP | other *** search
/ Programmer Power Tools / Programmer Power Tools.iso / turbopas / tutorpas.arc / TUTOR12.DOC < prev    next >
Encoding:
Text File  |  1989-06-05  |  33.0 KB  |  926 lines

  1. O
  2. PA A
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.                             LET'S BUILD A COMPILER!
  31.  
  32.                                        By
  33.  
  34.                             Jack W. Crenshaw, Ph.D.
  35.  
  36.                                   5 June 1989
  37.  
  38.  
  39.                               Part XII: MISCELLANY
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69. PA A
  70.  
  71.  
  72.  
  73.  
  74.  
  75.        *****************************************************************
  76.        *                                                               *
  77.        *                        COPYRIGHT NOTICE                       *
  78.        *                                                               *
  79.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  80.        *                                                               *
  81.        *****************************************************************
  82.  
  83.  
  84.        INTRODUCTION
  85.  
  86.        This installment is another one  of  those  excursions  into side
  87.        alleys  that  don't  seem to fit  into  the  mainstream  of  this
  88.        tutorial  series.    As I mentioned last time, it was while I was
  89.        writing this installment that I realized some changes  had  to be
  90.        made  to  the  compiler structure.  So I had to digress from this
  91.        digression long enough to develop the new structure  and  show it
  92.        to you.
  93.  
  94.        Now that that's behind us, I can tell you what I  set  out  to in
  95.        the first place.  This shouldn't  take  long, and then we can get
  96.        back into the mainstream.
  97.  
  98.        Several people have asked  me  about  things that other languages
  99.        provide, but so far I haven't addressed in this series.   The two
  100.        biggies are semicolons and  comments.    Perhaps  you've wondered
  101.        about them, too, and  wondered  how things would change if we had
  102.        to  deal with them.  Just so you can proceed with what's to come,
  103.        without being  bothered by that nagging feeling that something is
  104.        missing, we'll address such issues here.
  105.  
  106.  
  107.        SEMICOLONS
  108.  
  109.        Ever since the introduction of Algol, semicolons have been a part
  110.        of  almost every modern language.  We've all  used  them  to  the
  111.        point that they are taken for  granted.   Yet I suspect that more
  112.        compilation errors have  occurred  due  to  misplaced  or missing
  113.        semicolons  than  any  other single cause.  And if we had a penny
  114.        for  every  extra  keystroke programmers have used  to  type  the
  115.        little rascals, we could pay off the national debt.
  116.  
  117.        Having  been  brought  up with FORTRAN, it took me a long time to
  118.        get used to using semicolons, and to tell the  truth  I've  never
  119.        quite understood why they  were  necessary.    Since I program in
  120.        Pascal, and since the use of semicolons in Pascal is particularly
  121.        tricky,  that one little character is still  by  far  my  biggest
  122.        source of errors.
  123.  
  124.        When  I  began  developing  KISS,  I resolved to  question  EVERY
  125.        construct in other languages, and to try to avoid the most common
  126.        problems that occur with them.  That puts the semicolon very high
  127.        on my hit list.A6A6
  128.                                      - 2 -A*A*
  129.  
  130. PA A
  131.  
  132.  
  133.  
  134.  
  135.  
  136.        To  understand  the  role of the semicolon, you have to look at a
  137.        little history.
  138.  
  139.        Early programming languages were line-oriented.  In  FORTRAN, for
  140.        example, various parts  of  the statement had specific columns or
  141.        fields that they had to appear in.  Since  some  statements  were
  142.        too  long for one line, the  "continuation  card"  mechanism  was
  143.        provided to let  the  compiler  know  that a given card was still
  144.        part of the previous  line.   The mechanism survives to this day,
  145.        even though punched cards are now things of the distant past.
  146.  
  147.        When  other  languages  came  along,  they  also  adopted various
  148.        mechanisms for dealing with multiple-line statements.  BASIC is a
  149.        good  example.  It's important to  recognize,  though,  that  the
  150.        FORTRAN  mechanism  was   not   so  much  required  by  the  line
  151.        orientation of that  language,  as by the column-orientation.  In
  152.        those versions of FORTRAN  where  free-form  input  is permitted,
  153.        it's no longer needed.
  154.  
  155.        When the fathers  of  Algol introduced that language, they wanted
  156.        to get away  from  line-oriented programs like FORTRAN and BASIC,
  157.        and allow for free-form input.   This included the possibility of
  158.        stringing multiple statements on a single line, as in
  159.  
  160.  
  161.             a=b; c=d; e=e+1;
  162.  
  163.  
  164.        In cases like this,  the  semicolon is almost REQUIRED.  The same
  165.        line, without the semicolons, just looks "funny":
  166.  
  167.  
  168.             a=b c= d e=e+1
  169.  
  170.        I suspect that this is the major ... perhaps ONLY ...  reason for
  171.        semicolons: to keep programs from looking funny.
  172.  
  173.        But  the  idea  of stringing multiple statements  together  on  a
  174.        single  line  is  a  dubious  one  at  best.  It's not very  good
  175.        programming  style,  and  harks back to  the  days  when  it  was
  176.        considered improtant to conserve cards.  In these  days  of CRT's
  177.        and indented code, the clarity of programs is  far  better served
  178.        by  keeping statements separate.  It's still  nice  to  have  the
  179.        OPTION  of  multiple  statements,  but  it seems a shame to  keep
  180.        programmers  in  slavery  to the semicolon, just to keep that one
  181.        rare case from "looking funny."
  182.  
  183.        When I started in with KISS, I tried  to  keep  an  open mind.  I
  184.        decided that I would use  semicolons when it became necessary for
  185.        the parser, but not until then.  I figured this would happen just
  186.        about  the time I added the ability  to  spread  statements  over
  187.        multiple lines.  But, as you  can  see, that never happened.  The
  188.        TINY compiler is perfectly  happy  to  parse the most complicated
  189.        statement, spread over any number of lines, without semicolons.A*A*
  190.                                      - 3 -
  191.  
  192. PA A
  193.  
  194.  
  195.  
  196.  
  197.  
  198.        Still, there are people  who  have  used  semicolons for so long,
  199.        they feel naked  without them.  I'm one of them.  Once I had KISS
  200.        defined sufficiently well, I began to write a few sample programs
  201.        in the language.    I  discovered,  somewhat to my horror, that I
  202.        kept  putting  semicolons  in anyway.   So  now  I'm  facing  the
  203.        prospect of a NEW  rash  of  compiler  errors, caused by UNWANTED
  204.        semicolons.  Phooey!
  205.  
  206.        Perhaps more to the point, there are readers out  there  who  are
  207.        designing their own languages, which may  include  semicolons, or
  208.        who  want to use the techniques of  these  tutorials  to  compile
  209.        conventional languages like  C.    In  either case, we need to be
  210.        able to deal with semicolons.
  211.  
  212.  
  213.        SYNTACTIC SUGAR
  214.  
  215.        This whole discussion brings  up  the  issue of "syntactic sugar"
  216.        ... constructs that are added to a language, not because they are
  217.        needed, but because they help make the programs look right to the
  218.        programmer.    After  all, it's nice  to  have  a  small,  simple
  219.        compiler,    but  it  would  be  of  little  use if the resulting
  220.        language  were  cryptic  and hard to program.  The language FORTH
  221.        comes  to mind (a premature OUCH! for the  barrage  I  know  that
  222.        one's going to fetch me).  If we can add features to the language
  223.        that  make the programs easier to read  and  understand,  and  if
  224.        those features  help keep the programmer from making errors, then
  225.        we should do so.    Particularly if the constructs don't add much
  226.        to the complexity of the language or its compiler.
  227.  
  228.        The  semicolon  could  be considered an example,  but  there  are
  229.        plenty of others, such as the 'THEN' in a IF-statement,  the 'DO'
  230.        in a WHILE-statement,  and  even the 'PROGRAM' statement, which I
  231.        came within a gnat's eyelash of leaving out  of  TINY.    None of
  232.        these tokens  add  much  to  the  syntax  of the language ... the
  233.        compiler can figure out  what's  going on without them.  But some
  234.        folks feel that they  DO  add to the readability of programs, and
  235.        that can be very important.
  236.  
  237.        There are two schools of thought on this subject, which  are well
  238.        represented by two of our most popular languages, C and Pascal.
  239.  
  240.        To  the minimalists, all such sugar should be  left  out.    They
  241.        argue that it clutters up the language and adds to the  number of
  242.        keystrokes  programmers  must type.   Perhaps  more  importantly,
  243.        every extra token or keyword represents a trap laying in wait for
  244.        the inattentive programmer.  If you leave out  a  token, misplace
  245.        it, or misspell it, the compiler  will  get you.  So these people
  246.        argue that the best approach is to get rid of such things.  These
  247.        folks tend to like C, which has a minimum of unnecessary keywords
  248.        and punctuation.
  249.  
  250.        Those from the other school tend to like Pascal.  They argue that
  251.        having to type a few extra characters is a small price to pay forA*A*
  252.                                      - 4 -
  253.  
  254. PA A
  255.  
  256.  
  257.  
  258.  
  259.  
  260.        legibility.    After  all, humans have to read the programs, too.
  261.        Their best argument is that each such construct is an opportunity
  262.        to tell the compiler that you really mean for it  to  do what you
  263.        said to.  The sugary tokens serve as useful landmarks to help you
  264.        find your way.
  265.  
  266.        The differences are well represented by the two  languages.   The
  267.        most oft-heard complaint about  C  is  that  it is too forgiving.
  268.        When you make a mistake in C, the  erroneous  code  is  too often
  269.        another  legal  C  construct.    So  the  compiler  just  happily
  270.        continues to compile, and  leaves  you  to  find the error during
  271.        debug.    I guess that's why debuggers  are  so  popular  with  C
  272.        programmers.
  273.  
  274.        On the  other  hand,  if  a  Pascal  program compiles, you can be
  275.        pretty  sure that the program will do what you told it.  If there
  276.        is an error at run time, it's probably a design error.
  277.  
  278.        The  best  example  of  useful  sugar  is  the semicolon  itself.
  279.        Consider the code fragment:
  280.  
  281.  
  282.             a=1+(2*b+c)   b...
  283.  
  284.  
  285.        Since there is no operator connecting the token 'b' with the rest
  286.        of the  statement, the compiler will conclude that the expression
  287.        ends  with  the  ')', and the 'b'  is  the  beginning  of  a  new
  288.        statement.    But  suppose  I  have simply left out the  intended
  289.        operator, and I really want to say:
  290.  
  291.  
  292.             a=1+(2*b+c)*b...
  293.  
  294.  
  295.        In  this  case  the compiler will get an error, all right, but it
  296.        won't be very meaningful  since  it will be expecting an '=' sign
  297.        after the 'b' that really shouldn't be there.
  298.  
  299.        If, on the other hand, I include a semicolon after the  'b', THEN
  300.        there  can  be no doubt where I  intend  the  statement  to  end.
  301.        Syntactic  sugar,  then,  can  serve  a  very  useful purpose  by
  302.        providing some additional insurance that we remain on track.
  303.  
  304.        I find  myself  somewhere  in  the middle of all this.  I tend to
  305.        favor the Pascal-ers' view ... I'd much rather find  my  bugs  at
  306.        compile time rather than run time.  But I also hate to just throw
  307.        verbosity  in  for  no apparent reason, as in COBOL.  So far I've
  308.        consistently left most of the Pascal sugar out of KISS/TINY.  But
  309.        I certainly have no strong feelings either way, and  I  also  can
  310.        see the value of sprinkling a little sugar around  just  for  the
  311.        extra  insurance  that  it  brings.    If  you like  this  latter
  312.        approach, things like that are easy to add.  Just  remember that,A6A6
  313.                                      - 5 -A*A*
  314.  
  315. PA A
  316.  
  317.  
  318.  
  319.  
  320.  
  321.        like  the semicolon, each item of sugar  is  something  that  can
  322.        potentially cause a compile error by its omission.
  323.  
  324.  
  325.        DEALING WITH SEMICOLONS
  326.  
  327.        There  are  two  distinct  ways  in which semicolons are used  in
  328.        popular  languages.    In Pascal, the semicolon is regarded as an
  329.        statement SEPARATOR.  No semicolon  is  required  after  the last
  330.        statement in a block.  The syntax is:
  331.  
  332.  
  333.             <block> ::= <statement> ( ';' <statement>)*
  334.  
  335.             <statement> ::= <assignment> | <if> | <while> ... | null
  336.  
  337.  
  338.        (The null statement is IMPORTANT!)
  339.  
  340.        Pascal  also defines some semicolons in  other  places,  such  as
  341.        after the PROGRAM statement.
  342.  
  343.        In  C  and  Ada, on the other hand, the semicolon is considered a
  344.        statement TERMINATOR,  and  follows  all  statements  (with  some
  345.        embarrassing and confusing  exceptions).   The syntax for this is
  346.        simply:
  347.  
  348.  
  349.             <block> ::= ( <statement> ';')*
  350.  
  351.  
  352.        Of  the two syntaxes, the Pascal one seems on the face of it more
  353.        rational, but experience has shown  that it leads to some strange
  354.        difficulties.  People get  so  used  to  typing a semicolon after
  355.        every  statement  that  they tend to  type  one  after  the  last
  356.        statement in a block, also.  That usually doesn't cause  any harm
  357.        ...  it  just gets treated as a  null  statement.    Many  Pascal
  358.        programmers, including yours truly,  do  just  that. But there is
  359.        one  place you absolutely CANNOT type  a  semicolon,  and  that's
  360.        right before an ELSE.  This little gotcha  has  cost  me  many an
  361.        extra  compilation,  particularly  when  the  ELSE  is  added  to
  362.        existing code.    So  the  C/Ada  choice  turns out to be better.
  363.        Apparently Nicklaus Wirth thinks so, too:  In his  Modula  2,  he
  364.        abandoned the Pascal approach.
  365.  
  366.        Given either of these two syntaxes, it's an easy matter (now that
  367.        we've  reorganized  the  parser!) to add these  features  to  our
  368.        parser.  Let's take the last case first, since it's simpler.
  369.  
  370.        To begin, I've made things easy by introducing a new recognizer:
  371.  
  372.  
  373.        {--------------------------------------------------------------}
  374.        { Match a Semicolon }A*A*
  375.                                      - 6 -
  376.  
  377. PA A
  378.  
  379.  
  380.  
  381.  
  382.  
  383.        procedure Semi;
  384.        begin
  385.           MatchString(';');
  386.        end;
  387.        {--------------------------------------------------------------}
  388.  
  389.  
  390.        This procedure works very much like our old Match.  It insists on
  391.        finding a semicolon as the next token.  Having found it, it skips
  392.        to the next one.
  393.  
  394.        Since a  semicolon follows a statement, procedure Block is almost
  395.        the only one we need to change:
  396.  
  397.  
  398.        {--------------------------------------------------------------}
  399.        { Parse and Translate a Block of Statements }
  400.  
  401.        procedure Block;
  402.        begin
  403.           Scan;
  404.           while not(Token in ['e', 'l']) do begin
  405.              case Token of
  406.               'i': DoIf;
  407.               'w': DoWhile;
  408.               'R': DoRead;
  409.               'W': DoWrite;
  410.               'x': Assignment;
  411.              end;
  412.              Semi;
  413.              Scan;
  414.           end;
  415.        end;
  416.        {--------------------------------------------------------------}
  417.  
  418.  
  419.        Note carefully the subtle change in the case statement.  The call
  420.        to  Assignment  is now guarded by a test on Token.   This  is  to
  421.        avoid calling Assignment when the  token  is  a  semicolon (which
  422.        could happen if the statement is null).
  423.  
  424.        Since declarations are also  statements,  we  also  need to add a
  425.        call to Semi within procedure TopDecls:
  426.  
  427.  
  428.        {--------------------------------------------------------------}
  429.        { Parse and Translate Global Declarations }
  430.  
  431.        procedure TopDecls;
  432.        begin
  433.           Scan;
  434.           while Token = 'v' do begin
  435.              Alloc;
  436.              while Token = ',' doA*A*
  437.                                      - 7 -
  438.  
  439. PA A
  440.  
  441.  
  442.  
  443.  
  444.  
  445.                 Alloc;
  446.              Semi;
  447.           end;
  448.        end;
  449.        {--------------------------------------------------------------}
  450.  
  451.  
  452.        Finally, we need one for the PROGRAM statement:
  453.  
  454.  
  455.        {--------------------------------------------------------------}
  456.        { Main Program }
  457.  
  458.        begin
  459.           Init;
  460.           MatchString('PROGRAM');
  461.           Semi;
  462.           Header;
  463.           TopDecls;
  464.           MatchString('BEGIN');
  465.           Prolog;
  466.           Block;
  467.           MatchString('END');
  468.           Epilog;
  469.        end.
  470.        {--------------------------------------------------------------}
  471.  
  472.  
  473.        It's as easy as that.  Try it with a copy of TINY and see how you
  474.        like it.
  475.  
  476.        The Pascal version  is  a  little  trickier,  but  it  still only
  477.        requires  minor  changes,  and those only to procedure Block.  To
  478.        keep things as simple as possible, let's split the procedure into
  479.        two parts.  The following procedure handles just one statement:
  480.  
  481.  
  482.        {--------------------------------------------------------------}
  483.        { Parse and Translate a Single Statement }
  484.  
  485.        procedure Statement;
  486.        begin
  487.           Scan;
  488.           case Token of
  489.            'i': DoIf;
  490.            'w': DoWhile;
  491.            'R': DoRead;
  492.            'W': DoWrite;
  493.            'x': Assignment;
  494.           end;
  495.        end;
  496.        {--------------------------------------------------------------}ABAB
  497.                                      - 8 -A*A*
  498.  
  499. PA A
  500.  
  501.  
  502.  
  503.  
  504.  
  505.        Using this procedure, we can now rewrite Block like this:
  506.  
  507.  
  508.        {--------------------------------------------------------------}
  509.        { Parse and Translate a Block of Statements }
  510.  
  511.        procedure Block;
  512.        begin
  513.           Statement;
  514.           while Token = ';' do begin
  515.              Next;
  516.              Statement;
  517.           end;
  518.        end;
  519.        {--------------------------------------------------------------}
  520.  
  521.  
  522.        That  sure  didn't  hurt, did it?  We can now parse semicolons in
  523.        Pascal-like fashion.
  524.  
  525.  
  526.        A COMPROMISE
  527.  
  528.        Now that we know how to deal with semicolons, does that mean that
  529.        I'm going to put them in KISS/TINY?  Well, yes and  no.    I like
  530.        the extra sugar and the security that comes with knowing for sure
  531.        where the  ends  of  statements  are.    But I haven't changed my
  532.        dislike for the compilation errors associated with semicolons.
  533.  
  534.        So I have what I think is a nice compromise: Make them OPTIONAL!
  535.  
  536.        Consider the following version of Semi:
  537.  
  538.  
  539.        {--------------------------------------------------------------}
  540.        { Match a Semicolon }
  541.  
  542.        procedure Semi;
  543.        begin
  544.           if Token = ';' then Next;
  545.        end;
  546.        {--------------------------------------------------------------}
  547.  
  548.  
  549.        This procedure will ACCEPT a semicolon whenever it is called, but
  550.        it won't INSIST on one.  That means that when  you  choose to use
  551.        semicolons, the compiler  will  use the extra information to help
  552.        keep itself on track.  But if you omit one (or omit them all) the
  553.        compiler won't complain.  The best of both worlds.
  554.  
  555.        Put this procedure in place in the first version of  your program
  556.        (the  one for C/Ada syntax), and you have  the  makings  of  TINY
  557.        Version 1.2.A6A6
  558.                                      - 9 -A*A*
  559.  
  560. PA A
  561.  
  562.  
  563.  
  564.  
  565.  
  566.        COMMENTS
  567.  
  568.        Up  until  now  I have carefully avoided the subject of comments.
  569.        You would think that this would be an easy subject ... after all,
  570.        the compiler doesn't have to deal with comments at all; it should
  571.        just ignore them.  Well, sometimes that's true.
  572.  
  573.        Comments can be just about as easy or as difficult as  you choose
  574.        to make them.    At  one  extreme,  we can arrange things so that
  575.        comments  are  intercepted  almost  the  instant  they  enter the
  576.        compiler.  At the  other,  we can treat them as lexical elements.
  577.        Things  tend to get interesting when  you  consider  things  like
  578.        comment delimiters contained in quoted strings.
  579.  
  580.  
  581.        SINGLE-CHARACTER DELIMITERS
  582.  
  583.        Here's an example.  Suppose we assume the  Turbo  Pascal standard
  584.        and use curly braces for comments.  In this case we  have single-
  585.        character delimiters, so our parsing is a little easier.
  586.  
  587.        One  approach  is  to  strip  the  comments  out the  instant  we
  588.        encounter them in the input stream; that is,  right  in procedure
  589.        GetChar.    To  do  this,  first  change  the  name of GetChar to
  590.        something else, say GetCharX.  (For the record, this is  going to
  591.        be a TEMPORARY change, so best not do this with your only copy of
  592.        TINY.  I assume you understand that you should  always  do  these
  593.        experiments with a working copy.)
  594.  
  595.        Now, we're going to need a  procedure  to skip over comments.  So
  596.        key in the following one:
  597.  
  598.  
  599.        {--------------------------------------------------------------}
  600.        { Skip A Comment Field }
  601.  
  602.        procedure SkipComment;
  603.        begin
  604.           while Look <> '}' do
  605.              GetCharX;
  606.           GetCharX;
  607.        end;
  608.        {--------------------------------------------------------------}
  609.  
  610.  
  611.        Clearly, what this procedure is going to do is to simply read and
  612.        discard characters from the input  stream, until it finds a right
  613.        curly brace.  Then it reads one more character and returns  it in
  614.        Look.
  615.  
  616.        Now we can  write  a  new  version of GetChar that SkipComment to
  617.        strip out comments:ABAB
  618.                                     - 10 -A*A*
  619.  
  620. PA A
  621.  
  622.  
  623.  
  624.  
  625.  
  626.        {--------------------------------------------------------------}
  627.        { Get Character from Input Stream }
  628.        { Skip Any Comments }
  629.  
  630.        procedure GetChar;
  631.        begin
  632.           GetCharX;
  633.           if Look = '{' then SkipComment;
  634.        end;
  635.        {--------------------------------------------------------------}
  636.  
  637.  
  638.        Code this up  and  give  it  a  try.    You'll find that you can,
  639.        indeed, bury comments anywhere you like.  The comments never even
  640.        get into the parser proper ... every call to GetChar just returns
  641.        any character that's NOT part of a comment.
  642.  
  643.        As a matter of fact, while  this  approach gets the job done, and
  644.        may even be  perfectly  satisfactory  for  you, it does its job a
  645.        little  TOO  well.    First  of all, most  programming  languages
  646.        specify that a comment should be treated like a  space,  so  that
  647.        comments aren't allowed  to  be embedded in, say, variable names.
  648.        This current version doesn't care WHERE you put comments.
  649.  
  650.        Second, since the  rest  of  the  parser can't even receive a '{'
  651.        character, you will not be allowed to put one in a quoted string.
  652.  
  653.        Before you turn up your nose at this simplistic solution, though,
  654.        I should point out  that  as respected a compiler as Turbo Pascal
  655.        also won't allow  a  '{' in a quoted string.  Try it.  And as for
  656.        embedding a comment in an  identifier, I can't imagine why anyone
  657.        would want to do such a  thing,  anyway, so the question is moot.
  658.        For 99% of all  applications,  what I've just shown you will work
  659.        just fine.
  660.  
  661.        But,  if  you  want  to  be  picky  about it  and  stick  to  the
  662.        conventional treatment, then we  need  to  move  the interception
  663.        point downstream a little further.
  664.  
  665.        To  do  this,  first change GetChar back to the way  it  was  and
  666.        change the name called in SkipComment.  Then, let's add  the left
  667.        brace as a possible whitespace character:
  668.  
  669.  
  670.        {--------------------------------------------------------------}
  671.        { Recognize White Space }
  672.  
  673.        function IsWhite(c: char): boolean;
  674.        begin
  675.           IsWhite := c in [' ', TAB, CR, LF, '{'];
  676.        end;
  677.        {--------------------------------------------------------------}ABAB
  678.                                     - 11 -A*A*
  679.  
  680. PA A
  681.  
  682.  
  683.  
  684.  
  685.  
  686.        Now, we can deal with comments in procedure SkipWhite:
  687.  
  688.  
  689.        {--------------------------------------------------------------}
  690.        { Skip Over Leading White Space }
  691.  
  692.        procedure SkipWhite;
  693.        begin
  694.           while IsWhite(Look) do begin
  695.              if Look = '{' then
  696.                 SkipComment
  697.              else
  698.                 GetChar;
  699.           end;
  700.        end;
  701.        {--------------------------------------------------------------}
  702.  
  703.  
  704.        Note  that SkipWhite is written so that we  will  skip  over  any
  705.        combination of whitespace characters and comments, in one call.
  706.  
  707.        OK, give this one a try, too.   You'll  find  that  it will let a
  708.        comment serve to delimit tokens.  It's worth mentioning that this
  709.        approach also gives us the  ability to handle curly braces within
  710.        quoted strings, since within such  strings we will not be testing
  711.        for or skipping over whitespace.
  712.  
  713.        There's one last  item  to  deal  with:  Nested  comments.   Some
  714.        programmers like the idea  of  nesting  comments, since it allows
  715.        you to comment out code during debugging.  The  code  I've  given
  716.        here won't allow that and, again, neither will Turbo Pascal.
  717.  
  718.        But the fix is incredibly easy.  All  we  need  to  do is to make
  719.        SkipComment recursive:
  720.  
  721.  
  722.        {--------------------------------------------------------------}
  723.        { Skip A Comment Field }
  724.  
  725.        procedure SkipComment;
  726.        begin
  727.           while Look <> '}' do begin
  728.              GetChar;
  729.              if Look = '{' then SkipComment;
  730.           end;
  731.           GetChar;
  732.        end;
  733.        {--------------------------------------------------------------}
  734.  
  735.  
  736.        That does it.  As  sophisticated a comment-handler as you'll ever
  737.        need.ABAB
  738.                                     - 12 -A*A*
  739.  
  740. PA A
  741.  
  742.  
  743.  
  744.  
  745.  
  746.        MULTI-CHARACTER DELIMITERS
  747.  
  748.        That's all well and  good  for cases where a comment is delimited
  749.        by single  characters,  but  what  about  the  cases such as C or
  750.        standard Pascal, where two  characters  are  required?  Well, the
  751.        principles are still the same, but we have to change our approach
  752.        quite a bit.  I'm sure it won't surprise you to learn that things
  753.        get harder in this case.
  754.  
  755.        For the multi-character situation, the  easiest thing to do is to
  756.        intercept the left delimiter  back  at the GetChar stage.  We can
  757.        "tokenize" it right there, replacing it by a single character.
  758.  
  759.        Let's assume we're using the C delimiters '/*' and '*/'.   First,
  760.        we  need  to  go back to the "GetCharX' approach.  In yet another
  761.        copy of your compiler, rename  GetChar to GetCharX and then enter
  762.        the following new procedure GetChar:
  763.  
  764.  
  765.        {--------------------------------------------------------------}
  766.        { Read New Character.  Intercept '/*' }
  767.  
  768.        procedure GetChar;
  769.        begin
  770.           if TempChar <> ' ' then begin
  771.              Look := TempChar;
  772.              TempChar := ' ';
  773.              end
  774.           else begin
  775.              GetCharX;
  776.              if Look = '/' then begin
  777.                 Read(TempChar);
  778.                 if TempChar = '*' then begin
  779.                    Look := '{';
  780.                    TempChar := ' ';
  781.                 end;
  782.              end;
  783.           end;
  784.        end;
  785.        {--------------------------------------------------------------}
  786.  
  787.  
  788.        As you can see, what this procedure does is  to  intercept  every
  789.        occurrence of '/'.  It then examines the NEXT  character  in  the
  790.        stream.  If the character  is  a  '*',  then  we  have  found the
  791.        beginning  of  a  comment,  and  GetChar  will  return  a  single
  792.        character replacement for it.   (For  simplicity,  I'm  using the
  793.        same '{' character  as I did for Pascal.  If you were writing a C
  794.        compiler, you'd no doubt want to pick some other character that's
  795.        not  used  elsewhere  in C.  Pick anything you like ... even $FF,
  796.        anything that's unique.)
  797.  
  798.        If the character  following  the  '/'  is NOT a '*', then GetChar
  799.        tucks it away in the new global TempChar, and  returns  the  '/'.A*A*
  800.                                     - 13 -
  801.  
  802. PA A
  803.  
  804.  
  805.  
  806.  
  807.  
  808.        Note that you need to declare this new variable and initialize it
  809.        to ' '.  I like to do  things  like  that  using the Turbo "typed
  810.        constant" construct:
  811.  
  812.  
  813.             const TempChar: char = ' ';
  814.  
  815.  
  816.        Now we need a new version of SkipComment:
  817.  
  818.  
  819.        {--------------------------------------------------------------}
  820.        { Skip A Comment Field }
  821.  
  822.        procedure SkipComment;
  823.        begin
  824.           repeat
  825.              repeat
  826.                 GetCharX;
  827.              until Look = '*';
  828.              GetCharX;
  829.           until Look = '/';
  830.           GetChar;
  831.        end;
  832.        {--------------------------------------------------------------}
  833.  
  834.  
  835.        A  few  things  to  note:  first  of  all, function  IsWhite  and
  836.        procedure SkipWhite  don't  need  to  be  changed,  since GetChar
  837.        returns the '{' token.  If you change that token  character, then
  838.        of  course you also need to change the  character  in  those  two
  839.        routines.
  840.  
  841.        Second, note that  SkipComment  doesn't call GetChar in its loop,
  842.        but  GetCharX.    That  means   that  the  trailing  '/'  is  not
  843.        intercepted and  is seen by SkipComment.  Third, although GetChar
  844.        is the  procedure  doing  the  work,  we  can still deal with the
  845.        comment  characters  embedded  in  a  quoted  string,  by calling
  846.        GetCharX  instead  of  GetChar  while  we're  within  the string.
  847.        Finally,  note  that  we can again provide for nested comments by
  848.        adding a single statement to SkipComment, just as we did before.
  849.  
  850.  
  851.        ONE-SIDED COMMENTS
  852.  
  853.        So far I've shown you  how  to  deal  with  any  kind  of comment
  854.        delimited on the left and the  right.   That only leaves the one-
  855.        sided comments like those in assembler language or  in  Ada, that
  856.        are terminated by the end of the line.  In a  way,  that  case is
  857.        easier.   The only procedure that would need  to  be  changed  is
  858.        SkipComment, which must now terminate at the newline characters:
  859.  
  860.  
  861.        {--------------------------------------------------------------}A*A*
  862.                                     - 14 -
  863.  
  864. PA A
  865.  
  866.  
  867.  
  868.  
  869.  
  870.        { Skip A Comment Field }
  871.  
  872.        procedure SkipComment;
  873.        begin
  874.           repeat
  875.              GetCharX;
  876.           until Look = CR;
  877.           GetChar;
  878.        end;
  879.        {--------------------------------------------------------------}
  880.  
  881.  
  882.        If the leading character is  a  single  one,  as  in  the  ';' of
  883.        assembly language, then we're essentially done.  If  it's  a two-
  884.        character token, as in the '--'  of  Ada, we need only modify the
  885.        tests  within  GetChar.   Either way, it's an easier problem than
  886.        the balanced case.
  887.  
  888.  
  889.        CONCLUSION
  890.  
  891.        At this point we now have the ability to deal with  both comments
  892.        and semicolons, as well as other kinds of syntactic sugar.   I've
  893.        shown  you several ways to deal with  each,  depending  upon  the
  894.        convention  desired.    The  only  issue left is: which of  these
  895.        conventions should we use in KISS/TINY?
  896.  
  897.        For the reasons that I've given as we went  along,  I'm  choosing
  898.        the following:
  899.  
  900.  
  901.         (1) Semicolons are TERMINATORS, not separators
  902.  
  903.         (2) Semicolons are OPTIONAL
  904.  
  905.         (3) Comments are delimited by curly braces
  906.  
  907.         (4) Comments MAY be nested
  908.  
  909.  
  910.        Put the code corresponding to these cases into your copy of TINY.
  911.        You now have TINY Version 1.2.
  912.  
  913.        Now that we  have  disposed  of  these  sideline  issues,  we can
  914.        finally get back into the mainstream.  In  the  next installment,
  915.        we'll talk  about procedures and parameter passing, and we'll add
  916.        these important features to TINY.  See you then.
  917.  
  918.  
  919.        *****************************************************************
  920.        *                                                               *
  921.        *                        COPYRIGHT NOTICE                       *
  922.        *                                                               *
  923.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *A*A*
  924.                                     - 15 -
  925.  
  926. PA A
  927.  
  928.  
  929.  
  930.  
  931.  
  932.        *                                                               *
  933.        *****************************************************************AUAU
  934. ASAS
  935.  
  936.  
  937.  
  938.  
  939.  
  940.  
  941.                                     - 16 -A*A*
  942. @